using System;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using System.Collections.Generic;
using System.Security.Cryptography;

/// <summary>
/// A class file to aide in working with ASN.1 encoded
/// objects such as PKCS#8 PrivateKeyInfo messages and
/// X.509 PublicKeyInfo messages. Useful for exporting
/// a RSA or DSA key for use in Java and other non-XML
/// encoded key aware languages.
/// </summary>
///
/// <remarks>
/// Jeffrey Walton
/// </remarks>

namespace CSInteropSign
{
  class AsnKeyBuilder
  {
    internal class AsnMessage
    {
      private byte[] m_octets;
      private String m_format;

      internal int Length
      {
        get
        {
          if (null == m_octets) { return 0; }
          return m_octets.Length;
        }
        // set { m_length = value; }
      }

      internal AsnMessage(byte[] octets, String format)
      {
        m_octets = octets;
        m_format = format;
      }

      internal byte[] GetBytes()
      {
        if (null == m_octets)
        { return new byte[] { }; }

        return m_octets;
      }
      internal String GetFormat()
      { return m_format; }
    }

    internal class AsnType
    {
      // Constructors
      // No default - must specify tag and data

      public AsnType(byte tag, byte octet)
      {
        m_raw = false;
        m_tag = new byte[] { tag };
        m_octets = new byte[] { octet };
      }

      public AsnType(byte tag, byte[] octets)
      {
        m_raw = false;
        m_tag = new byte[] { tag };
        m_octets = octets;
      }

      public AsnType(byte tag, byte[] length, byte[] octets)
      {
        m_raw = true;
        m_tag = new byte[] { tag };
        m_length = length;
        m_octets = octets;
      }

      private bool m_raw;

      private bool Raw
      {
        get { return m_raw; }
        set { m_raw = value; }
      }

      // Setters and Getters
      private byte[] m_tag;
      public byte[] Tag
      {
        get
        {
          if (null == m_tag)
            return EMPTY;
          return m_tag;
        }
        // set { m_tag = value; }
      }

      private byte[] m_length;
      public byte[] Length
      {
        get
        {
          if (null == m_length)
            return EMPTY;
          return m_length;
        }
        // set { m_length = value; }
      }

      private byte[] m_octets;
      public byte[] Octets
      {
        get
        {
          if (null == m_octets)
          { return EMPTY; }
          return m_octets;
        }
        set
        { m_octets = value; }
      }

      // Methods
      internal byte[] GetBytes()
      {
        // Created raw by user
        // return the bytes....
        if (true == m_raw)
        {
          return Concatenate(
            new byte[][] { m_tag, m_length, m_octets }
          );
        }

        SetLength();

        // Special case
        // Null does not use length
        if (0x05 == m_tag[0])
        {
          return Concatenate(
            new byte[][] { m_tag, m_octets }
          );
        }

        return Concatenate(
          new byte[][] { m_tag, m_length, m_octets }
        );
      }

      private void SetLength()
      {
        if (null == m_octets)
        {
          m_length = ZERO;
          return;
        }

        // Special case
        // Null does not use length
        if (0x05 == m_tag[0])
        {
          m_length = EMPTY;
          return;
        }

        byte[] length = null;

        // Length: 0 <= l < 0x80
        if (m_octets.Length < 0x80)
        {
          length = new byte[1];
          length[0] = (byte)m_octets.Length;
        }
        // 0x80 < length <= 0xFF
        else if (m_octets.Length <= 0xFF)
        {
          length = new byte[2];
          length[0] = 0x81;
          length[1] = (byte)((m_octets.Length & 0xFF));
        }

        //
        // We should almost never see these...
        //

        // 0xFF < length <= 0xFFFF
        else if (m_octets.Length <= 0xFFFF)
        {
          length = new byte[3];
          length[0] = 0x82;
          length[1] = (byte)((m_octets.Length & 0xFF00) >> 8);
          length[2] = (byte)((m_octets.Length & 0xFF));
        }

        // 0xFFFF < length <= 0xFFFFFF
        else if (m_octets.Length <= 0xFFFFFF)
        {
          length = new byte[4];
          length[0] = 0x83;
          length[1] = (byte)((m_octets.Length & 0xFF0000) >> 16);
          length[2] = (byte)((m_octets.Length & 0xFF00) >> 8);
          length[3] = (byte)((m_octets.Length & 0xFF));
        }
        // 0xFFFFFF < length <= 0xFFFFFFFF
        else
        {
          length = new byte[5];
          length[0] = 0x84;
          length[1] = (byte)((m_octets.Length & 0xFF000000) >> 24);
          length[2] = (byte)((m_octets.Length & 0xFF0000) >> 16);
          length[3] = (byte)((m_octets.Length & 0xFF00) >> 8);
          length[4] = (byte)((m_octets.Length & 0xFF));
        }

        m_length = length;
      }

      private byte[] Concatenate(byte[][] values)
      {
        // Nothing in, nothing out
        if (IsEmpty(values))
          return new byte[] { };

        int length = 0;
        foreach (byte[] b in values)
        {
          if (null != b) length += b.Length;
        }

        byte[] cated = new byte[length];

        int current = 0;
        foreach (byte[] b in values)
        {
          if (null != b)
          {
            Array.Copy(b, 0, cated, current, b.Length);
            current += b.Length;
          }
        }

        return cated;
      }
    };

    private static byte[] ZERO = new byte[] { 0 };
    private static byte[] EMPTY = new byte[] { };

    // PublicKeyInfo (X.509 compatible) message
    /// <summary>
    /// Returns the AsnMessage representing the X.509 PublicKeyInfo.
    /// </summary>
    /// <param name="publicKey">The DSA key to be encoded.</param>
    /// <returns>Returns the AsnType representing the
    /// X.509 PublicKeyInfo.</returns>
    /// <seealso cref="PrivateKeyToPKCS8(DSAParameters)"/>
    /// <seealso cref="PrivateKeyToPKCS8(RSAParameters)"/>
    /// <seealso cref="PublicKeyToX509(RSAParameters)"/>
    internal static AsnMessage PublicKeyToX509(DSAParameters publicKey)
    {
      // Value Type cannot be null
      // Debug.Assert(null != publicKey);

      /* *
      * SEQUENCE              // PrivateKeyInfo
      * +- SEQUENCE           // AlgorithmIdentifier
      * |  +- OID             // 1.2.840.10040.4.1
      * |  +- SEQUENCE        // DSS-Params (Optional Parameters)
      * |    +- INTEGER (P)
      * |    +- INTEGER (Q)
      * |    +- INTEGER (G)
      * +- BITSTRING          // PublicKey
      *    +- INTEGER(Y)      // DSAPublicKey Y
      * */

      // DSA Parameters
      AsnType p = CreateIntegerPos(publicKey.P);
      AsnType q = CreateIntegerPos(publicKey.Q);
      AsnType g = CreateIntegerPos(publicKey.G);

      // Sequence - DSA-Params
      AsnType dssParams = CreateSequence(new AsnType[] { p, q, g });

      // OID - packed 1.2.840.10040.4.1
      //   { 0x2A, 0x86, 0x48, 0xCE, 0x38, 0x04, 0x01 }
      AsnType oid = CreateOid("1.2.840.10040.4.1");

      // Sequence
      AsnType algorithmID = CreateSequence(new AsnType[] { oid, dssParams });

      // Public Key Y
      AsnType y = CreateIntegerPos(publicKey.Y);
      AsnType key = CreateBitString(y);

      // Sequence 'A'
      AsnType publicKeyInfo =
        CreateSequence(new AsnType[] { algorithmID, key });

      return new AsnMessage(publicKeyInfo.GetBytes(), "X.509");
    }

    // PublicKeyInfo (X.509 compatible) message
    /// <summary>
    /// Returns the AsnMessage representing the X.509 PublicKeyInfo.
    /// </summary>
    /// <param name="publicKey">The RSA key to be encoded.</param>
    /// <returns>Returns the AsnType representing the
    /// X.509 PublicKeyInfo.</returns>
    /// <seealso cref="PrivateKeyToPKCS8(DSAParameters)"/>
    /// <seealso cref="PrivateKeyToPKCS8(RSAParameters)"/>
    /// <seealso cref="PublicKeyToX509(DSAParameters)"/>
    internal static AsnMessage PublicKeyToX509(RSAParameters publicKey)
    {
      // Value Type cannot be null
      // Debug.Assert(null != publicKey);

      /* *
      * SEQUENCE              // PrivateKeyInfo
      * +- SEQUENCE           // AlgorithmIdentifier
      *    +- OID             // 1.2.840.113549.1.1.1
      *    +- Null            // Optional Parameters
      * +- BITSTRING          // PrivateKey
      *    +- SEQUENCE        // RSAPrivateKey
      *       +- INTEGER(N)   // N
      *       +- INTEGER(E)   // E
      * */

      // OID - packed 1.2.840.113549.1.1.1
      //   { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }
      AsnType oid = CreateOid("1.2.840.113549.1.1.1");
      AsnType algorithmID =
        CreateSequence(new AsnType[] { oid, CreateNull() });

      AsnType n = CreateIntegerPos(publicKey.Modulus);
      AsnType e = CreateIntegerPos(publicKey.Exponent);
      AsnType key = CreateBitString(
        CreateSequence(new AsnType[] { n, e })
      );

      AsnType publicKeyInfo =
        CreateSequence(new AsnType[] { algorithmID, key });

      return new AsnMessage(publicKeyInfo.GetBytes(), "X.509");
    }

    // PKCS #8, Section 6 (PrivateKeyInfo) message
    // !!!!!!!!!!!!!!! Unencrypted !!!!!!!!!!!!!!!
    /// <summary>
    /// Returns AsnMessage representing the unencrypted
    /// PKCS #8 PrivateKeyInfo.
    /// </summary>
    /// <param name="privateKey">The DSA key to be encoded.</param>
    /// <returns>Returns the AsnType representing the unencrypted
    /// PKCS #8 PrivateKeyInfo.</returns>
    /// <seealso cref="PrivateKeyToPKCS8(RSAParameters)"/>
    /// <seealso cref="PublicKeyToX509(DSAParameters)"/>
    /// <seealso cref="PublicKeyToX509(RSAParameters)"/>
    internal static AsnMessage PrivateKeyToPKCS8(DSAParameters privateKey)
    {
      // Value Type cannot be null
      // Debug.Assert(null != privateKey);

      /* *
      * SEQUENCE              // PrivateKeyInfo
      * +- INTEGER(0)         // Version (v1998)
      * +- SEQUENCE           // AlgorithmIdentifier
      * |  +- OID             // 1.2.840.10040.4.1
      * |  +- SEQUENCE        // DSS-Params (Optional Parameters)
      * |    +- INTEGER (P)
      * |    +- INTEGER (Q)
      * |    +- INTEGER (G)
      * +- OCTETSTRING        // PrivateKey
      *    +- INTEGER(X)   // DSAPrivateKey X
      * */

      // Version - 0 (v1998)
      AsnType version = CreateInteger(ZERO);

      // Domain Parameters
      AsnType p = CreateIntegerPos(privateKey.P);
      AsnType q = CreateIntegerPos(privateKey.Q);
      AsnType g = CreateIntegerPos(privateKey.G);

      AsnType dssParams = CreateSequence(new AsnType[] { p, q, g });

      // OID - packed 1.2.840.10040.4.1
      //   { 0x2A, 0x86, 0x48, 0xCE, 0x38, 0x04, 0x01 }
      AsnType oid = CreateOid("1.2.840.10040.4.1");

      // AlgorithmIdentifier
      AsnType algorithmID = CreateSequence(new AsnType[] { oid, dssParams });

      // Private Key X
      AsnType x = CreateIntegerPos(privateKey.X);
      AsnType key = CreateOctetString(x);

      // Sequence
      AsnType privateKeyInfo =
        CreateSequence(new AsnType[] { version, algorithmID, key });

      return new AsnMessage(privateKeyInfo.GetBytes(), "PKCS#8");
    }

    // PKCS #8, Section 6 (PrivateKeyInfo) message
    // !!!!!!!!!!!!!!! Unencrypted !!!!!!!!!!!!!!!
    /// <summary>
    /// Returns AsnMessage representing the unencrypted
    /// PKCS #8 PrivateKeyInfo.
    /// </summary>
    /// <param name="privateKey">The RSA key to be encoded.</param>
    /// <returns>Returns the AsnType representing the unencrypted
    /// PKCS #8 PrivateKeyInfo.</returns>
    /// <seealso cref="PrivateKeyToPKCS8(DSAParameters)"/>
    /// <seealso cref="PublicKeyToX509(DSAParameters)"/>
    /// <seealso cref="PublicKeyToX509(RSAParameters)"/>
    internal static AsnMessage PrivateKeyToPKCS8(RSAParameters privateKey)
    {
      // Value Type cannot be null
      // Debug.Assert(null != privateKey);

      /* *
      * SEQUENCE                  // PublicKeyInfo
      * +- INTEGER(0)             // Version - 0 (v1998)
      * +- SEQUENCE               // AlgorithmIdentifier
      *    +- OID                 // 1.2.840.113549.1.1.1
      *    +- NULL                // Optional Parameters
      * +- OCTETSTRING            // PrivateKey
      *    +- SEQUENCE            // RSAPrivateKey
      *       +- INTEGER(0)       // Version - 0 (v1998)
      *       +- INTEGER(N)
      *       +- INTEGER(E)
      *       +- INTEGER(D)
      *       +- INTEGER(P)
      *       +- INTEGER(Q)
      *       +- INTEGER(DP)
      *       +- INTEGER(DQ)
      *       +- INTEGER(Inv Q)
      * */

      AsnType n = CreateIntegerPos(privateKey.Modulus);
      AsnType e = CreateIntegerPos(privateKey.Exponent);
      AsnType d = CreateIntegerPos(privateKey.D);
      AsnType p = CreateIntegerPos(privateKey.P);
      AsnType q = CreateIntegerPos(privateKey.Q);
      AsnType dp = CreateIntegerPos(privateKey.DP);
      AsnType dq = CreateIntegerPos(privateKey.DQ);
      AsnType iq = CreateIntegerPos(privateKey.InverseQ);

      // Version - 0 (v1998)
      AsnType version = CreateInteger(new byte[] { 0 });

      // octstring = OCTETSTRING(SEQUENCE(INTEGER(0)INTEGER(N)...))
      AsnType key = CreateOctetString(
        CreateSequence(new AsnType[] { version, n, e, d, p, q, dp, dq, iq })
      );

      // OID - packed 1.2.840.113549.1.1.1
      //   { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }
      AsnType algorithmID = CreateSequence(new AsnType[] { CreateOid("1.2.840.113549.1.1.1"), CreateNull() }
      );

      // PrivateKeyInfo
      AsnType privateKeyInfo =
        CreateSequence(new AsnType[] { version, algorithmID, key });

      return new AsnMessage(privateKeyInfo.GetBytes(), "PKCS#8");
    }

    /// <summary>
    /// <para>An ordered collection of one or more types.
    /// Returns the AsnType representing an ASN.1 encoded sequence.</para>
    /// <para>If the AsnType is null, an empty sequence (length 0)
    /// is returned.</para>
    /// </summary>
    /// <param name="value">An AsnType consisting of
    /// a single value to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded sequence.</returns>
    /// <seealso cref="CreateSet(AsnType)"/>
    /// <seealso cref="CreateSet(AsnType[])"/> 
    /// <seealso cref="CreateSetOf(AsnType)"/>
    /// <seealso cref="CreateSetOf(AsnType[])"/>
    /// <seealso cref="CreateSequence(AsnType)"/>
    /// <seealso cref="CreateSequence(AsnType[])"/>
    /// <seealso cref="CreateSequenceOf(AsnType)"/>
    /// <seealso cref="CreateSequenceOf(AsnType[])"/>
    internal static AsnType CreateSequence(AsnType value)
    {
      // Should be at least 1...
      Debug.Assert(!IsEmpty(value));

      // One or more required
      if (IsEmpty(value))
      { throw new ArgumentException("A sequence requires at least one value."); }

      // Sequence: Tag 0x30 (16, Universal, Constructed)
      return new AsnType(0x30, value.GetBytes());
    }

    /// <summary>
    /// <para>An ordered collection of one or more types.
    /// Returns the AsnType representing an ASN.1 encoded sequence.</para>
    /// <para>If the AsnType is null, an
    /// empty sequence (length 0) is returned.</para>
    /// </summary>
    /// <param name="values">An array of AsnType consisting of
    /// the values in the collection to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded Set.</returns>
    /// <seealso cref="CreateSet(AsnType)"/>
    /// <seealso cref="CreateSet(AsnType[])"/> 
    /// <seealso cref="CreateSetOf(AsnType)"/>
    /// <seealso cref="CreateSetOf(AsnType[])"/>
    /// <seealso cref="CreateSequence(AsnType)"/>
    /// <seealso cref="CreateSequence(AsnType[])"/>
    /// <seealso cref="CreateSequenceOf(AsnType)"/>
    /// <seealso cref="CreateSequenceOf(AsnType[])"/>
    internal static AsnType CreateSequence(AsnType[] values)
    {
      // Should be at least 1...
      Debug.Assert(!IsEmpty(values));

      // One or more required
      if (IsEmpty(values))
      { throw new ArgumentException("A sequence requires at least one value."); }

      // Sequence: Tag 0x30 (16, Universal, Constructed)
      return new AsnType((0x10 | 0x20), Concatenate(values));
    }

    /// <summary>
    /// <para>An ordered collection zero, one or more types.
    /// Returns the AsnType representing an ASN.1 encoded sequence.</para>
    /// <para>If the AsnType value is null,an
    /// empty sequence (length 0) is returned.</para>
    /// </summary>
    /// <param name="value">An AsnType consisting of
    /// a single value to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded sequence.</returns>
    /// <seealso cref="CreateSet(AsnType)"/>
    /// <seealso cref="CreateSet(AsnType[])"/> 
    /// <seealso cref="CreateSetOf(AsnType)"/>
    /// <seealso cref="CreateSetOf(AsnType[])"/>
    /// <seealso cref="CreateSequence(AsnType)"/>
    /// <seealso cref="CreateSequence(AsnType[])"/>
    /// <seealso cref="CreateSequenceOf(AsnType)"/>
    /// <seealso cref="CreateSequenceOf(AsnType[])"/>
    internal static AsnType CreateSequenceOf(AsnType value)
    {
      // From the ASN.1 Mailing List
      if (IsEmpty(value))
      { return new AsnType(0x30, EMPTY); }

      // Sequence: Tag 0x30 (16, Universal, Constructed)
      return new AsnType(0x30, value.GetBytes());
    }

    /// <summary>
    /// <para>An ordered collection zero, one or more types.
    /// Returns the AsnType representing an ASN.1 encoded sequence.</para>
    /// <para>If the AsnType array is null or the array is 0 length,
    /// an empty sequence (length 0) is returned.</para>
    /// </summary>
    /// <param name="values">An AsnType consisting of
    /// the values in the collection to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded sequence.</returns>
    /// <seealso cref="CreateSet(AsnType)"/>
    /// <seealso cref="CreateSet(AsnType[])"/> 
    /// <seealso cref="CreateSetOf(AsnType)"/>
    /// <seealso cref="CreateSetOf(AsnType[])"/>
    /// <seealso cref="CreateSequence(AsnType)"/>
    /// <seealso cref="CreateSequence(AsnType[])"/>
    /// <seealso cref="CreateSequenceOf(AsnType)"/>
    /// <seealso cref="CreateSequenceOf(AsnType[])"/>
    internal static AsnType CreateSequenceOf(AsnType[] values)
    {
      // From the ASN.1 Mailing List
      if (IsEmpty(values))
      { return new AsnType(0x30, EMPTY); }

      // Sequence: Tag 0x30 (16, Universal, Constructed)
      return new AsnType(0x30, Concatenate(values));
    }

    /// <summary>
    /// <para>An ordered sequence of zero, one or more bits. Returns
    /// the AsnType representing an ASN.1 encoded bit string.</para>
    /// <para>If octets is null or length is 0, an empty (0 length)
    /// bit string is returned.</para>
    /// </summary>
    /// <param name="octets">A MSB (big endian) byte[] representing the
    /// bit string to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded bit string.</returns>
    /// <seealso cref="CreateBitString(byte[], uint)"/>
    /// <seealso cref="CreateBitString(AsnType)"/>
    /// <seealso cref="CreateBitString(AsnType[])"/>
    /// <seealso cref="CreateBitString(String)"/>
    /// <seealso cref="CreateOctetString(byte[])"/>
    /// <seealso cref="CreateOctetString(AsnType)"/>
    /// <seealso cref="CreateOctetString(AsnType[])"/>
    /// <seealso cref="CreateOctetString(String)"/>
    internal static AsnType CreateBitString(byte[] octets)
    {
      // BitString: Tag 0x03 (3, Universal, Primitive)
      return CreateBitString(octets, 0);
    }

    /// <summary>
    /// <para>An ordered sequence of zero, one or more bits. Returns
    /// the AsnType representing an ASN.1 encoded bit string.</para>
    /// <para>unusedBits is applied to the end of the bit string,
    /// not the start of the bit string. unusedBits must be less than 8
    /// (the size of an octet). Refer to ITU X.680, Section 32.</para>
    /// <para>If octets is null or length is 0, an empty (0 length)
    /// bit string is returned.</para>
    /// </summary>
    /// <param name="octets">A MSB (big endian) byte[] representing the
    /// bit string to be encoded.</param>
    /// <param name="unusedBits">The number of unused trailing binary
    /// digits in the bit string to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded bit string.</returns>
    /// <seealso cref="CreateBitString(byte[])"/>
    /// <seealso cref="CreateBitString(AsnType)"/>
    /// <seealso cref="CreateBitString(AsnType[])"/>
    /// <seealso cref="CreateBitString(String)"/>
    /// <seealso cref="CreateOctetString(byte[])"/>
    /// <seealso cref="CreateOctetString(AsnType)"/>
    /// <seealso cref="CreateOctetString(AsnType[])"/>
    /// <seealso cref="CreateOctetString(String)"/>
    internal static AsnType CreateBitString(byte[] octets, uint unusedBits)
    {
      if (IsEmpty(octets))
      {
        // Empty octet string
        return new AsnType(0x03, EMPTY);
      }

      if (!(unusedBits < 8))
      { throw new ArgumentException("Unused bits must be less than 8."); }

      byte[] b = Concatenate(new byte[] { (byte)unusedBits }, octets);
      // BitString: Tag 0x03 (3, Universal, Primitive)
      return new AsnType(0x03, b);
    }

    /// <summary>
    /// An ordered sequence of zero, one or more bits. Returns
    /// the AsnType representing an ASN.1 encoded bit string.
    /// If value is null, an empty (0 length) bit string is
    /// returned.
    /// </summary>
    /// <param name="value">An AsnType object to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded bit string.</returns>
    /// <seealso cref="CreateBitString(byte[])"/>
    /// <seealso cref="CreateBitString(byte[], uint)"/>
    /// <seealso cref="CreateBitString(AsnType[])"/>
    /// <seealso cref="CreateBitString(String)"/>
    /// <seealso cref="CreateOctetString(byte[])"/>
    /// <seealso cref="CreateOctetString(AsnType)"/>
    /// <seealso cref="CreateOctetString(AsnType[])"/>
    /// <seealso cref="CreateOctetString(String)"/>
    internal static AsnType CreateBitString(AsnType value)
    {
      if (IsEmpty(value))
      { return new AsnType(0x03, EMPTY); }

      // BitString: Tag 0x03 (3, Universal, Primitive)
      return CreateBitString(value.GetBytes(), 0x00);
    }

    /// <summary>
    /// An ordered sequence of zero, one or more bits. Returns
    /// the AsnType representing an ASN.1 encoded bit string.
    /// If value is null, an empty (0 length) bit string is
    /// returned.
    /// </summary>
    /// <param name="values">An AsnType object to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded bit string.</returns>
    /// <seealso cref="CreateBitString(byte[])"/>
    /// <seealso cref="CreateBitString(byte[], uint)"/>
    /// <seealso cref="CreateBitString(AsnType)"/>
    /// <seealso cref="CreateBitString(String)"/>
    /// <seealso cref="CreateOctetString(byte[])"/>
    /// <seealso cref="CreateOctetString(AsnType)"/>
    /// <seealso cref="CreateOctetString(AsnType[])"/>
    /// <seealso cref="CreateOctetString(String)"/>
    internal static AsnType CreateBitString(AsnType[] values)
    {
      if (IsEmpty(values))
      { return new AsnType(0x03, EMPTY); }

      // BitString: Tag 0x03 (3, Universal, Primitive)
      return CreateBitString(Concatenate(values), 0x00);
    }

    /// <summary>
    /// <para>An ordered sequence of zero, one or more bits. Returns
    /// the AsnType representing an ASN.1 encoded bit string.</para>
    /// <para>If octets is null or length is 0, an empty (0 length)
    /// bit string is returned.</para>
    /// <para>If conversion fails, the bit string returned is a partial
    /// bit string. The partial bit string ends at the octet before the
    /// point of failure (it does not include the octet which could
    /// not be parsed, or subsequent octets).</para>
    /// </summary>
    /// <param name="value">A MSB (big endian) byte[] representing the
    /// bit string to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded bit string.</returns>
    /// <seealso cref="CreateBitString(byte[])"/>
    /// <seealso cref="CreateBitString(byte[], uint)"/>
    /// <seealso cref="CreateBitString(AsnType)"/>
    /// <seealso cref="CreateOctetString(byte[])"/>
    /// <seealso cref="CreateOctetString(AsnType)"/>
    /// <seealso cref="CreateOctetString(AsnType[])"/>
    /// <seealso cref="CreateOctetString(String)"/>
    internal static AsnType CreateBitString(String value)
    {
      if (IsEmpty(value))
      { return CreateBitString(EMPTY); }

      // Any unused bits?
      int lstrlen = value.Length;
      int unusedBits = 8 - (lstrlen % 8);
      if (8 == unusedBits) { unusedBits = 0; }

      for (int i = 0; i < unusedBits; i++)
      { value += "0"; }

      // Determine number of octets
      int loctlen = (lstrlen + 7) / 8;

      List<byte> octets = new List<byte>();
      for (int i = 0; i < loctlen; i++)
      {
        String s = value.Substring(i * 8, 8);
        byte b = 0x00;

        try
        { b = Convert.ToByte(s, 2); }

        catch (FormatException /*e*/) { unusedBits = 0; break; }
        catch (OverflowException /*e*/) { unusedBits = 0; break; }

        octets.Add(b);
      }

      // BitString: Tag 0x03 (3, Universal, Primitive)
      return CreateBitString(octets.ToArray(), (uint)unusedBits);
    }

    /// <summary>
    /// An ordered sequence of zero, one or more octets. Returns
    /// the ASN.1 encoded octet string. If octets is null or length
    /// is 0, an empty (0 length) octet string is returned.
    /// </summary>
    /// <param name="value">A MSB (big endian) byte[] representing the
    /// octet string to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded octet string.</returns>
    /// <seealso cref="CreateBitString(byte[])"/>
    /// <seealso cref="CreateBitString(byte[], uint)"/>
    /// <seealso cref="CreateBitString(AsnType)"/>
    /// <seealso cref="CreateBitString(String)"/>
    /// <seealso cref="CreateOctetString(AsnType)"/>
    /// <seealso cref="CreateOctetString(AsnType[])"/>
    /// <seealso cref="CreateOctetString(String)"/>
    internal static AsnType CreateOctetString(byte[] value)
    {
      if (IsEmpty(value))
      {
        // Empty octet string
        return new AsnType(0x04, EMPTY);
      }

      // OctetString: Tag 0x04 (4, Universal, Primitive)
      return new AsnType(0x04, value);
    }

    /// <summary>
    /// An ordered sequence of zero, one or more octets. Returns
    /// the byte[] representing an ASN.1 encoded octet string.
    /// If octets is null or length is 0, an empty (0 length)
    /// o ctet string is returned.
    /// </summary>
    /// <param name="value">An AsnType object to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded octet string.</returns>
    /// <seealso cref="CreateBitString(byte[])"/>
    /// <seealso cref="CreateBitString(byte[], uint)"/>
    /// <seealso cref="CreateBitString(AsnType)"/>
    /// <seealso cref="CreateBitString(String)"/>
    /// <seealso cref="CreateOctetString(byte[])"/>
    /// <seealso cref="CreateOctetString(String)"/>
    internal static AsnType CreateOctetString(AsnType value)
    {
      if (IsEmpty(value))
      {
        // Empty octet string
        return new AsnType(0x04, 0x00);
      }

      // OctetString: Tag 0x04 (4, Universal, Primitive)
      return new AsnType(0x04, value.GetBytes());
    }

    /// <summary>
    /// An ordered sequence of zero, one or more octets. Returns
    /// the byte[] representing an ASN.1 encoded octet string.
    /// If octets is null or length is 0, an empty (0 length)
    /// o ctet string is returned.
    /// </summary>
    /// <param name="values">An AsnType object to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded octet string.</returns>
    /// <seealso cref="CreateBitString(byte[])"/>
    /// <seealso cref="CreateBitString(byte[], uint)"/>
    /// <seealso cref="CreateBitString(AsnType)"/>
    /// <seealso cref="CreateBitString(String)"/>
    /// <seealso cref="CreateOctetString(byte[])"/>
    /// <seealso cref="CreateOctetString(AsnType)"/>
    /// <seealso cref="CreateOctetString(String)"/>
    internal static AsnType CreateOctetString(AsnType[] values)
    {
      if (IsEmpty(values))
      {
        // Empty octet string
        return new AsnType(0x04, 0x00);
      }

      // OctetString: Tag 0x04 (4, Universal, Primitive)
      return new AsnType(0x04, Concatenate(values));
    }

    /// <summary>
    /// <para>An ordered sequence of zero, one or more bits. Returns
    /// the AsnType representing an ASN.1 encoded octet string.</para>
    /// <para>If octets is null or length is 0, an empty (0 length)
    /// octet string is returned.</para>
    /// <para>If conversion fails, the bit string returned is a partial
    /// bit string. The partial octet string ends at the octet before the
    /// point of failure (it does not include the octet which could
    /// not be parsed, or subsequent octets).</para>
    /// </summary>
    /// <param name="value">A string representing the
    /// octet string to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded octet string.</returns>
    /// <seealso cref="CreateBitString(byte[])"/>
    /// <seealso cref="CreateBitString(byte[], uint)"/>
    /// <seealso cref="CreateBitString(String)"/>
    /// <seealso cref="CreateBitString(AsnType)"/>
    /// <seealso cref="CreateOctetString(byte[])"/>
    /// <seealso cref="CreateOctetString(AsnType)"/>
    /// <seealso cref="CreateOctetString(AsnType[])"/>
    internal static AsnType CreateOctetString(String value)
    {
      if (IsEmpty(value))
      { return CreateOctetString(EMPTY); }

      // Determine number of octets
      int len = (value.Length + 255) / 256;

      List<byte> octets = new List<byte>();
      for (int i = 0; i < len; i++)
      {
        String s = value.Substring(i * 2, 2);
        byte b = 0x00;

        try
        { b = Convert.ToByte(s, 16); }
        catch (FormatException /*e*/) { break; }
        catch (OverflowException /*e*/) { break; }

        octets.Add(b);
      }

      // OctetString: Tag 0x04 (4, Universal, Primitive)
      return CreateOctetString(octets.ToArray());
    }

    /// <summary>
    /// <para>Returns the AsnType representing a ASN.1 encoded
    /// integer. The octets pass through this method are not modified.</para>
    /// <para>If octets is null or zero length, the method returns an
    /// AsnType equivalent to CreateInteger(byte[]{0})..</para>
    /// </summary>
    /// <param name="value">A MSB (big endian) byte[] representing the
    /// integer to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded integer.</returns>
    /// <example>
    /// ASN.1 encoded 0:
    /// <code>CreateInteger(null)</code>
    /// <code>CreateInteger(new byte[]{0x00})</code>
    /// <code>CreateInteger(new byte[]{0x00, 0x00})</code>
    /// </example>
    /// <example>
    /// ASN.1 encoded 1:
    /// <code>CreateInteger(new byte[]{0x01})</code>
    /// </example>
    /// <seealso cref="CreateIntegerPos"/>
    /// <seealso cref="CreateIntegerNeg"/>
    internal static AsnType CreateInteger(byte[] value)
    {
      // Is it better to add a '0', or silently
      //   drop the Integer? Dropping integers
      //   is probably not te best choice...
      if (IsEmpty(value))
      { return CreateInteger(ZERO); }

      return new AsnType(0x02, value);
    }

    /// <summary>
    /// <para>Returns the AsnType representing a positive ASN.1 encoded
    /// integer. If the high bit of most significant byte is set,
    /// the method prepends a 0x00 to octets before assigning the
    /// value to ensure the resulting integer is interpreted as
    /// positive in the application.</para>
    /// <para>If octets is null or zero length, the method returns an
    /// AsnType equivalent to CreateInteger(byte[]{0})..</para>
    /// </summary>
    /// <param name="value">A MSB (big endian) byte[] representing the
    /// integer to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded positive integer.</returns>
    /// <example>
    /// ASN.1 encoded 0:
    /// <code>CreateIntegerPos(null)</code>
    /// <code>CreateIntegerPos(new byte[]{0x00})</code>
    /// <code>CreateIntegerPos(new byte[]{0x00, 0x00})</code>
    /// </example>
    /// <example>
    /// ASN.1 encoded 1:
    /// <code>CreateInteger(new byte[]{0x01})</code>
    /// </example>
    /// <seealso cref="CreateInteger"/>
    /// <seealso cref="CreateIntegerNeg"/>
    internal static AsnType CreateIntegerPos(byte[] value)
    {
      byte[] i = null, d = Duplicate(value);

      if (IsEmpty(d)) { d = ZERO; }

      // Mediate the 2's compliment representation.
      // If the first byte has its high bit set, we will
      // add the additional byte of 0x00
      if (d.Length > 0 && d[0] > 0x7F)
      {
        i = new byte[d.Length + 1];
        i[0] = 0x00;
        Array.Copy(d, 0, i, 1, value.Length);
      }
      else
      {
        i = d;
      }

      // Integer: Tag 0x02 (2, Universal, Primitive)
      return CreateInteger(i);
    }

    /// <summary>
    /// <para>Returns the negative ASN.1 encoded integer. If the high
    /// bit of most significant byte is set, the integer is already
    /// considered negative.</para>
    /// <para>If the high bit of most significant byte
    /// is <bold>not</bold> set, the integer will be 2's complimented
    /// to form a negative integer.</para>
    /// <para>If octets is null or zero length, the method returns an
    /// AsnType equivalent to CreateInteger(byte[]{0})..</para>
    /// </summary>
    /// <param name="value">A MSB (big endian) byte[] representing the
    /// integer to be encoded.</param>
    /// <returns>Returns the negative ASN.1 encoded integer.</returns>
    /// <example>
    /// ASN.1 encoded 0:
    /// <code>CreateIntegerNeg(null)</code>
    /// <code>CreateIntegerNeg(new byte[]{0x00})</code>
    /// <code>CreateIntegerNeg(new byte[]{0x00, 0x00})</code>
    /// </example>
    /// <example>
    /// ASN.1 encoded -1 (2's compliment 0xFF):
    /// <code>CreateIntegerNeg(new byte[]{0x01})</code>
    /// </example>
    /// <example>
    /// ASN.1 encoded -2 (2's compliment 0xFE):
    /// <code>CreateIntegerNeg(new byte[]{0x02})</code>
    /// </example>
    /// <example>
    /// ASN.1 encoded -1:
    /// <code>CreateIntegerNeg(new byte[]{0xFF})</code>
    /// <code>CreateIntegerNeg(new byte[]{0xFF,0xFF})</code>
    /// Note: already negative since the high bit is set.</example>
    /// <example>
    /// ASN.1 encoded -255 (2's compliment 0xFF, 0x01):
    /// <code>CreateIntegerNeg(new byte[]{0x00,0xFF})</code>
    /// </example>
    /// <example>
    /// ASN.1 encoded -255 (2's compliment 0xFF, 0xFF, 0x01):
    /// <code>CreateIntegerNeg(new byte[]{0x00,0x00,0xFF})</code>
    /// </example>
    /// <seealso cref="CreateInteger"/>
    /// <seealso cref="CreateIntegerPos"/>
    internal static AsnType CreateIntegerNeg(byte[] value)
    {
      // Is it better to add a '0', or silently
      //   drop the Integer? Dropping integers
      //   is probably not te best choice...
      if (IsEmpty(value))
      { return CreateInteger(ZERO); }

      // No Trimming
      // The byte[] may be that way for a reason
      if (IsZero(value))
      { return CreateInteger(value); }

      //
      // At this point, we know we have at least 1 octet
      //

      // Is this integer already negative?
      if (value[0] >= 0x80)
      // Pass through with no modifications
      { return CreateInteger(value); }

      // No need to Duplicate - Compliment2s
      // performs the action
      byte[] c = Compliment2s(value);

      return CreateInteger(c);
    }

    /// <summary>
    /// Returns the AsnType representing an ASN.1 encoded null.
    /// </summary>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded null.</returns>
    internal static AsnType CreateNull()
    {
      return new AsnType(0x05, new byte[] { 0x00 });
    }

    /// <summary>
    /// Removes leading 0x00 octets from the byte[] octets. This
    /// method may return an empty byte array (0 length).
    /// </summary>
    /// <param name="octets">An array of octets to trim.</param>
    /// <returns>A byte[] with leading 0x00 octets removed.</returns>
    internal static byte[] TrimStart(byte[] octets)
    {
      if (IsEmpty(octets) || IsZero(octets))
      { return new byte[] { }; }

      byte[] d = Duplicate(octets);

      // Position of the first non-zero value
      int pos = 0;
      foreach (byte b in d)
      {
        if (0 != b) { break; }
        pos++;
      }

      // Nothing to trim
      if (pos == d.Length)
      { return octets; }

      // Allocate trimmed array
      byte[] t = new byte[d.Length - pos];

      // Copy
      Array.Copy(d, pos, t, 0, t.Length);

      return t;
    }

    /// <summary>
    /// Removes trailing 0x00 octets from the byte[] octets. This
    /// method may return an empty byte array (0 length).
    /// </summary>
    /// <param name="octets">An array of octets to trim.</param>
    /// <returns>A byte[] with trailing 0x00 octets removed.</returns>
    internal static byte[] TrimEnd(byte[] octets)
    {
      if (IsEmpty(octets) || IsZero(octets))
      { return EMPTY; }

      byte[] d = Duplicate(octets);

      Array.Reverse(d);

      d = TrimStart(d);

      Array.Reverse(d);

      return d;
    }

    /// <summary>
    /// Returns the AsnType representing an ASN.1 encoded OID.
    /// If conversion fails, the result is a partial conversion
    /// up to the point of failure. If the oid string is null or
    /// not well formed, an empty byte[] is returned.
    /// </summary>
    /// <param name="value">The string representing the object
    /// identifier to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded object identifier.</returns>
    /// <example>The following assigns the encoded AsnType
    /// for a RSA key to oid:
    /// <code>AsnType oid = CreateOid("1.2.840.113549.1.1.1")</code>
    /// </example>
    /// <seealso cref="CreateOid(byte[])"/>
    internal static AsnType CreateOid(String value)
    {
      // Punt?
      if (IsEmpty(value))
        return null;

      String[] tokens = value.Split(new Char[] { ' ', '.' });

      // Punt?
      if (IsEmpty(tokens))
        return null;

      // Parsing/Manipulation of the arc value
      UInt64 a = 0;

      // One or more strings are available
      List<UInt64> arcs = new List<UInt64>();

      foreach (String t in tokens)
      {
        // No empty or ill-formed strings...
        if (t.Length == 0) { break; }

        try { a = Convert.ToUInt64(t, CultureInfo.InvariantCulture); }
        catch (FormatException /*e*/) { break; }
        catch (OverflowException /*e*/) { break; }

        arcs.Add(a);
      }

      // Punt?
      if (0 == arcs.Count)
        return null;

      // Octets to be returned to caller
      List<byte> octets = new List<byte>();

      // Guard the case of a small list
      // The list has at least 1 item...    
      if (arcs.Count >= 1) { a = arcs[0] * 40; }
      if (arcs.Count >= 2) { a += arcs[1]; }
      octets.Add((byte)(a));

      // Add remaining arcs (subidentifiers)
      for (int i = 2; i < arcs.Count; i++)
      {
        // Scratch list builder for this arc
        List<byte> temp = new List<byte>();

        // The current arc (subidentifier)
        UInt64 arc = arcs[i];

        // Build the arc (subidentifier) byte array
        // The array is built in reverse (LSB to MSB).
        do
        {
          // Each entry is formed from the low 7 bits (0x7F).
          // Set high bit of all entries (0x80) per X.680. We
          // will unset the high bit of the final byte later.
          temp.Add((byte)(0x80 | (arc & 0x7F)));
          arc >>= 7;
        } while (0 != arc);

        // Grab resulting array. Because of the do/while,
        // there is at least one value in the array.
        byte[] t = temp.ToArray();

        // Unset high bit of byte t[0]
        // t[0] will be LSB after the array is reversed.
        t[0] = (byte)(0x7F & t[0]);

        // MSB first...
        Array.Reverse(t);

        // Add to the resulting array
        foreach (byte b in t)
        { octets.Add(b); }
      }

      return CreateOid(octets.ToArray());
    }

    /// <summary>
    /// Returns the AsnType representing an ASN.1 encoded OID.
    /// If conversion fails, the result is a partial conversion
    /// (up to the point of failure). If octets is null, an
    /// empty byte[] is returned.
    /// </summary>
    /// <param name="value">The packed byte[] representing the object
    /// identifier to be encoded.</param>
    /// <returns>Returns the AsnType representing an ASN.1
    /// encoded object identifier.</returns>
    /// <example>The following assigns the encoded AsnType for a RSA
    /// key to oid:
    /// <code>// Packed 1.2.840.113549.1.1.1
    /// byte[] rsa = new byte[] { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
    /// AsnType = CreateOid(rsa)</code>
    /// </example>
    /// <seealso cref="CreateOid(String)"/>
    internal static AsnType CreateOid(byte[] value)
    {
      // Punt...
      if (IsEmpty(value))
      { return null; }

      // OID: Tag 0x06 (6, Universal, Primitive)
      return new AsnType(0x06, value);
    }

    private static byte[] Compliment1s(byte[] value)
    {
      if (IsEmpty(value))
      { return EMPTY; }

      // Make a copy of octet array
      byte[] c = Duplicate(value);

      for (int i = c.Length - 1; i >= 0; i--)
      {
        // Compliment
        c[i] = (byte)~c[i];
      }

      return c;
    }

    private static byte[] Compliment2s(byte[] value)
    {
      if (IsEmpty(value))
      { return EMPTY; }

      // 2s Compliment of 0 is 0
      if (IsZero(value))
      { return Duplicate(value); }

      // Make a copy of octet array
      byte[] d = Duplicate(value);

      int carry = 1;
      for (int i = d.Length - 1; i >= 0; i--)
      {
        // Compliment
        d[i] = (byte)~d[i];

        // Add
        int j = d[i] + carry;

        // Write Back
        d[i] = (byte)(j & 0xFF);

        // Determine Next Carry
        if (0x100 == (j & 0x100))
        { carry = 1; }
        else
        { carry = 0; }
      }

      // Carry Array (we may need to carry out of 'd'
      byte[] c = null;
      if (1 == carry)
      {
        c = new byte[d.Length + 1];

        // Sign Extend....
        c[0] = (byte)0xFF;

        Array.Copy(d, 0, c, 1, d.Length);
      }
      else
      {
        c = d;
      }

      return c;
    }

    private static byte[] Concatenate(AsnType[] values)
    {
      // Nothing in, nothing out
      if (IsEmpty(values))
        return new byte[] { };

      int length = 0;
      foreach (AsnType t in values)
      {
        if (null != t)
        { length += t.GetBytes().Length; }
      }

      byte[] cated = new byte[length];

      int current = 0;
      foreach (AsnType t in values)
      {
        if (null != t)
        {
          byte[] b = t.GetBytes();

          Array.Copy(b, 0, cated, current, b.Length);
          current += b.Length;
        }
      }

      return cated;
    }

    private static byte[] Concatenate(byte[] first, byte[] second)
    {
      return Concatenate(new byte[][] { first, second });
    }

    private static byte[] Concatenate(byte[][] values)
    {
      // Nothing in, nothing out
      if (IsEmpty(values))
        return new byte[] { };

      int length = 0;
      foreach (byte[] b in values)
      {
        if (null != b)
        { length += b.Length; }
      }

      byte[] cated = new byte[length];

      int current = 0;
      foreach (byte[] b in values)
      {
        if (null != b)
        {
          Array.Copy(b, 0, cated, current, b.Length);
          current += b.Length;
        }
      }

      return cated;
    }

    private static byte[] Duplicate(byte[] b)
    {
      if (IsEmpty(b))
      { return EMPTY; }

      byte[] d = new byte[b.Length];
      Array.Copy(b, d, b.Length);

      return d;
    }

    private static bool IsZero(byte[] octets)
    {
      if (IsEmpty(octets))
      { return false; }

      bool allZeros = true;
      for (int i = 0; i < octets.Length; i++)
      {
        if (0 != octets[i])
        { allZeros = false; break; }
      }
      return allZeros;
    }

    private static bool IsEmpty(byte[] octets)
    {
      if (null == octets || 0 == octets.Length)
      { return true; }

      return false;
    }

    private static bool IsEmpty(String s)
    {
      if (null == s || 0 == s.Length)
      { return true; }

      return false;
    }

    private static bool IsEmpty(String[] strings)
    {
      if (null == strings || 0 == strings.Length)
        return true;

      return false;
    }

    private static bool IsEmpty(AsnType value)
    {
      if (null == value)
      { return true; }

      return false;
    }

    private static bool IsEmpty(AsnType[] values)
    {
      if (null == values || 0 == values.Length)
        return true;

      return false;
    }

    private static bool IsEmpty(byte[][] arrays)
    {
      if (null == arrays || 0 == arrays.Length)
        return true;

      return false;
    }
  }
}